跳到主要内容

TypeScript 文件模块化

声明文件

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。

这里是涉及到的一些关键字

  • declare var 声明全局变量
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明(含有子属性的)全局对象
  • interface 和 type 声明全局类型
  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6 默认导出
  • export = commonjs 导出模块
  • export as namespace UMD 库声明全局变量
  • declare global 扩展全局变量
  • declare module 扩展模块
  • /// <reference /> 三斜线指令
  • export declare 其实这个就和直接使用 declare 一样,只不过前者需要使用 import 导入,后者直接用(使用 declare 主要是为了兼容老模块)

在不同的场景下,声明文件的内容和使用方式会有所区别。

库的使用场景主要有以下几种:

  • 全局变量:通过 <script> 标签引入第三方库,注入全局变量
  • npm 包:通过 import foo from 'foo' 导入,符合 ES6 模块规范
  • UMD 库:既可以通过 <script> 标签引入,又可以通过 import 导入
  • 直接扩展全局变量:通过 <script> 标签引入后,改变一个全局变量的结构
  • 在 npm 包或 UMD 库中扩展全局变量:引用 npm 包或 UMD 库后,改变一个全局变量的结构
  • 模块插件:通过 <script>import 导入后,改变另一个模块的结构

什么是声明文件

通常我们会把声明语句放到一个单独的文件(例如 jQuery.d.ts)中,这就是声明文件

// src/jQuery.d.ts

declare var jQuery: (selector: string) => any;

声明文件必需以 .d.ts 为后缀 一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。所以当我们将 jQuery.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 jQuery 的类型定义了。

什么是声明语句(declare 关键字)

假如我们想使用第三方库 ,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。

$('#foo');
// or
jQuery('#foo');

但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西,所以需要使用 declare var 来定义它的类型

declare var jQuery: (selector: string) => any;

jQuery('#foo');

declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:

jQuery('#foo');

第三方声明文件

可以使用 @types 统一管理第三方库的声明文件。

@types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:

npm install @types/jquery --save-dev

可以通过:TypeSearch 搜索需要的第三方声明文件

全局变量

使用全局变量的声明文件时,如果是以 npm install @types/xxx --save-dev 安装的,则不需要任何配置。如果是将声明文件直接存放于当前项目中,则建议和其他源码一起放到 src 目录下(或者对应的源码目录下):

/path/to/project
├── src
| ├── index.ts
| └── my_file.d.ts
└── tsconfig.json

全局变量的声明文件主要有以下几种语法:

  • declare var 声明全局变量
  • declare function 声明全局方法
  • declare class 声明全局类
  • declare enum 声明全局枚举类型
  • declare namespace 声明(含有子属性的)全局对象
  • interface 和 type 声明全局类型

declare var 声明全局变量

// src/my_file.d.ts
// 使用 let 和 var 没有区别
// 当使用 const 定义时,表示此时的全局变量是一个常量
declare let myPara: (selector: string) => any;

// src/index.ts
myPara('#foo');
// 使用 declare let 定义的 myPara 类型,允许修改这个全局变量
myPara = function(selector) {
return document.querySelector(selector);
};

需要注意的是,声明语句中只能定义类型,切勿在声明语句中定义具体的实现

declare function 声明全局方法

// src/my_file.d.ts
declare function myFun: (selector: string) :any;
// 在函数类型的声明语句中,函数重载也是支持的
declare function myFun(domReadyCallback: () => any): any;

// src/index.ts
myFun('#foo');
myFun(function() {
alert('Dom Ready!');
});

declare class 声明全局类

// src/Animal.d.ts

declare class Animal {
name: string;
constructor(name: string);
sayHi(): string;
}

// src/index.ts

let cat = new Animal('Tom');

同样的,declare class 语句也只能用来定义类型,不能用来定义具体的实现

declare enum 声明全局枚举类型

// src/Directions.d.ts

declare enum Directions {
Up,
Down,
Left,
Right
}

// src/index.ts

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

interface 和 type 声明全局类型:除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,可以直接使用 interfacetype 来声明一个全局的接口或类型(interface 前是不需要 declare 的)

// src/jQuery.d.ts

interface AjaxSettings {
method?: 'GET' | 'POST'
data?: any;
}
declare namespace jQuery {
function ajax(url: string, settings?: AjaxSettings): void;
}

// src/index.ts

let settings: AjaxSettings = {
method: 'POST',
data: {
name: 'foo'
}
};
jQuery.ajax('/api/post_something', settings);

npm 包

npm 包的声明文件主要有以下几种语法:

  • export 导出变量
  • export namespace 导出(含有子属性的)对象
  • export default ES6 默认导出
  • export = commonjs 导出模块

tsconfig.json 内容

{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*": ["types/*"]
}
}
}

npm 包的声明文件与全局变量的声明文件有很大区别。在 npm 包的声明文件中,使用 declare 不再会声明一个全局变量,而只会在当前文件中声明一个局部变量。

只有在声明文件中使用 export 导出,然后在使用方 import 导入后,才会应用到这些类型声明。

export 导出变量

// types/foo/index.d.ts

export const name: string;
export function getName(): string;
export class Animal {
constructor(name: string);
sayHi(): string;
}
export enum Directions {
Up,
Down,
Left,
Right
}
export interface Options {
data: any;
}

对应的导入和使用模块应该是这样:

// src/index.ts

import { name, getName, Animal, Directions, Options } from 'foo';

console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
data: {
name: 'foo'
}
};

混用 declareexport 可以使用 declare 先声明多个变量,最后再用 export 一次性导出

// types/foo/index.d.ts

declare const name: string;
declare function getName(): string;
declare class Animal {
constructor(name: string);
sayHi(): string;
}
declare enum Directions {
Up,
Down,
Left,
Right
}
interface Options {
data: any;
}

export { name, getName, Animal, Directions, Options };

与全局变量的声明文件类似,interface 前是不需要 declare 的。

ES6 默认导出

在 ES6 模块系统中,使用 export default 可以导出一个默认值,使用方可以用 import foo from 'foo' 而不是 import { foo } from 'foo' 来导入这个默认值。

// types/foo/index.d.ts

export default function foo(): string;

// src/index.ts

import foo from 'foo';

foo();

注意,只有 functionclassinterface 可以直接默认导出,其他的变量需要先定义出来,再默认导出(例如下面的枚举类型)

// types/foo/index.d.ts

declare enum Directions {
Up,
Down,
Left,
Right
}

export default Directions;

commonjs 导出模块

// 整体导出
module.exports = foo;
// 单个导出
exports.bar = bar;

在 ts 中,针对这种模块导出,有多种方式可以导入,第一种方式是 const ... = require

// 整体导入
const foo = require('foo');
// 单个导入
const bar = require('foo').bar;

第二种方式是 import ... from,注意针对整体导出,需要使用 import * as 来导入:

// 整体导入
import * as foo from 'foo';
// 单个导入
import { bar } from 'foo';

第三种方式是 import ... require,这也是 ts 官方推荐的方式:

// 整体导入
import foo = require('foo');
// 单个导入
import bar = foo.bar;

三斜线指令

一个声明文件有时会依赖另一个声明文件中的类型

// types/moment-plugin/index.d.ts

import * as moment from 'moment';

declare module 'moment' {
export function foo(): moment.CalendarKey;
}

除了可以在声明文件中通过 import 导入另一个声明文件中的类型之外,还有一个语法也可以用来导入另一个声明文件,那就是三斜线指令。

namespace 类似,三斜线指令也是 ts 在早期版本中为了描述模块之间的依赖关系而创造的语法。随着 ES6 的广泛应用,现在已经不建议再使用 ts 中的三斜线指令来声明模块之间的依赖关系了。

// types/jquery-plugin/index.d.ts

/// <reference types="jquery" />

declare function foo(options: JQuery.AjaxSettings): string;

Reference

参考资料 声明文件